fix(gui): resolve assets for HID++ 1.0 Unifying devices via WPID fallback - #751
fix(gui): resolve assets for HID++ 1.0 Unifying devices via WPID fallback#751luisfacarmo wants to merge 6 commits into
Conversation
…back
Devices using HID++ 1.0 (many Unifying keyboards like K540/K545, K375s,
etc.) report model_info with all-zero model_ids because they lack feature
0x0003. The normal asset resolution path fails for these since neither the
strict nor suffix candidates match the registry.
This commit adds a WPID-based fallback: when the normal resolve() returns
None and the device has a WPID, try resolve_by_wpid() which formats the
WPID as a 4-hex suffix and matches it against the asset registry's modelId.
This works because the Unifying WPID IS the eQuad transport PID.
Additionally, persist_identities() now guards against identity downgrade:
a resolved display name (e.g. "K540/K545") cannot be overwritten by a
fallback name ("Slot 3") when the asset resolver temporarily fails.
Tested on hardware: Logitech K540/K545 (wpid 0x4076) via Unifying receiver
on Debian 13 Trixie. The device correctly shows its product name and
front.png asset across multiple inventory refresh cycles.
Fixes devices that have assets in the registry but were showing as
"Slot N" or "Unknown device" due to HID++ 1.0 limitations.
Greptile SummaryThe PR adds WPID-based asset resolution and persists WPIDs to stabilize HID++ 1.0 device identities. It also adds anti-downgrade handling and a legacy-identity backfill, but the backfill can permanently associate an old identity with a replacement device.
Confidence Score: 4/5The PR is not yet safe to merge because the legacy WPID migration can permanently bind a replacement device to the previous occupant's persisted identity. A receiver slot is reused across pairings, and the new backfill writes the live WPID into a legacy identity precisely when no codename, model information, or prior WPID proves that the live device is the identity's original product. Files Needing Attention: crates/openlogi-desktop/src/state/inventory.rs
|
| Filename | Overview |
|---|---|
| crates/openlogi-desktop/src/state/inventory.rs | Adds WPID persistence and anti-downgrade matching, but the legacy backfill cannot distinguish the original device from a same-kind replacement occupying the reused slot. |
| crates/openlogi-desktop/src/state/devices.rs | Adds WPID asset fallback, persisted-name recovery, and WPID propagation into device records. |
| crates/openlogi-desktop/src/services/assets.rs | Adds registry asset lookup using a four-hex-digit WPID suffix with codename fallback. |
| crates/openlogi-core/src/config/device.rs | Extends persisted device identities with an optional backward-compatible WPID field. |
| crates/openlogi-desktop/src/state/tests.rs | Adds regression coverage for backfilling a missing WPID while preserving resolved metadata, but does not cover slot reuse before the first backfill. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Legacy identity without WPID] --> B[Different same-kind device enters reused receiver slot]
B --> C[Live record has WPID but no codename or model info]
C --> D[same_model_identity returns true]
D --> E[Preserve old identity]
E --> F[Backfill replacement WPID into old identity]
F --> G[Stale metadata remains associated across refreshes]
Reviews (9): Last reviewed commit: "fix(gui): backfill WPID into legacy iden..." | Re-trigger Greptile
Address review feedback: when a Unifying/Bolt slot is re-paired with a different device type (e.g. keyboard replaced by mouse), the anti-downgrade guard must not preserve the previous occupant's identity. Adding a kind check ensures the guard only fires when the device classification matches, so a re-pairing correctly starts fresh.
70b3e6d to
d85a266
Compare
d85a266 to
9d0c8b4
Compare
…ce kind The identity-preservation guards in persist_identities() and the display-name fallback in build_device_list() previously used only DeviceKind as the discriminator to decide whether a persisted identity should carry over. This allowed a same-kind re-pair (e.g. swapping one mouse for another in the same receiver slot) to inherit the old device's display name, capabilities, and model metadata. Strengthen both checks: compare codename and model_info.config_key() when available, so a different product model paired into the same slot is correctly detected as a re-pairing. When no model-level identifiers are available on either side, the conservative kind-only fallback is retained (no regression for devices that never expose richer metadata).
9d0c8b4 to
44dcd85
Compare
The wpid field was added to DeviceIdentity in this branch but two pre-existing tests in config/tests.rs constructed the struct without it, causing E0063 on CI.
…guard When the anti-downgrade guard preserves a resolved identity over a fallback-quality incoming one, check whether the persisted identity lacks a WPID while the live record provides one. If so, patch the WPID into the existing identity. This ensures legacy identities (created before WPID persistence existed) acquire a discriminator, allowing same_model_identity to detect re-pairings into the same receiver slot by different HID++ 1.0 devices on subsequent inventory cycles. Adds a regression test that seeds a legacy identity without WPID, presents a fallback-quality live device with a known WPID, and asserts both that the WPID is backfilled and that the display_name is not downgraded.
| if existing.wpid.is_none() | ||
| && let Some(live_wpid) = record.wpid | ||
| && live_wpid != 0 | ||
| { | ||
| let mut patched = existing.clone(); | ||
| patched.wpid = Some(live_wpid); | ||
| config.set_device_identity(config_key, patched); | ||
| changed = true; | ||
| } |
There was a problem hiding this comment.
Legacy backfill cements stale identity
When a different same-kind HID++ 1.0 device first occupies a receiver slot whose legacy identity has no WPID, codename, or model information, same_model_identity accepts the match and this branch assigns the replacement's WPID to the previous occupant's identity. Subsequent unresolved refreshes then preserve the previous display name, artwork, capabilities, and registry identity for the replacement device.
Knowledge Base Used: Core domain and configuration
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
HID++ 1.0 Unifying devices (many keyboards like K540/K545, K375s, etc.) report
DeviceModelInfowith all-zeromodel_idsbecause they lack feature 0x0003. The normal asset resolution path fails for these since neither the strict nor suffix candidates match the registry — even though the registry contains their assets keyed by WPID.Changes
crates/openlogi-desktop/src/services/assets.rsresolve_by_wpid()method onAssetResolverthat formats the WPID as a 4-hex suffix and matches against the registry viafind_by_model_id_suffix. Falls back to codename/displayName matching.crates/openlogi-desktop/src/state/devices.rsbuild_device_list(): whencache.resolve(model, codename)returnsNone, fall back tocache.resolve_by_wpid(paired.wpid, paired.codename). This covers the case wheremodel_infoisSomebut contains all-zero IDs.crates/openlogi-desktop/src/state/inventory.rspersist_identities(): a resolved identity (one with a real product name) cannot be overwritten by a fallback identity ("Slot 3") when the resolver temporarily fails.identity_is_resolved()andis_fallback_display_name()helpers.Testing
Tested on real hardware: Logitech K540/K545 (WPID
0x4076) via Unifying receiver on Debian 13 Trixie.front.pngasset in carousel and detail viewHow to test on hardware:
OPENLOGI_LOG=debug openlogi list— should showwpid=4076for the keyboardFixes #(new issue — HID++ 1.0 Unifying devices show as "Slot N" despite having assets in the registry)